package com.easypoi.util;

import java.lang.reflect.Method;
import java.lang.reflect.ReflectPermission;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;


public class Reflector {

	private static final Map<Class<?>, Reflector> cacheReflectUtil = new HashMap<Class<?>, Reflector>();

	private Class<?> type;
	private Map<String, Method> getMethods = new HashMap<String, Method>();
	private Map<String, Method> setMethods = new HashMap<String, Method>();

	/**
	 * 私有化构造方法，添加初始化内容
	 * 
	 * @param clazz
	 */
	private Reflector(Class<?> clazz) {
		type = clazz;
		addGetMethods(clazz);
		addSetMethods(clazz);
	}

	/**
	 * 处理set方法
	 * 
	 * @param clazz
	 */
	private void addSetMethods(Class<?> clazz) {
		Map<String, List<Method>> conflectSetter = new HashMap<>();
		Method[] methods = getClassMethods(clazz);
		for (Method method : methods) {
			String name = method.getName();
			if (name.startsWith("set")) {
				String property = methodNameToProperty(name);
				addConflectMethod(conflectSetter, property, method);
			}
		}
		// 解决冲突
		resolveSetterConflect(conflectSetter);
	}

	private void resolveSetterConflect(Map<String, List<Method>> conflectSetter) {
		for (String propName : conflectSetter.keySet()) {
			List<Method> setters = conflectSetter.get(propName);
			Method firstMethod = setters.get(0);
			if (setters.size() == 1) {
				addSetMethod(propName, firstMethod);
			} else {
				Method setter = firstMethod;
				Class<?> setterType = firstMethod.getReturnType();
				Iterator<Method> iterator = setters.iterator();
				Method method = null;
				while (iterator.hasNext()) {
					method = iterator.next();
					Class<?> resultType = method.getReturnType();
					if (setterType.equals(resultType)) {
						throw new RuntimeException("Illegal overloaded getter method with ambiguous type for property "
								+ propName + " in class " + firstMethod.getDeclaringClass()
								+ ".  This breaks the JavaBeans "
								+ "specification and can cause unpredicatble results.");
					} else if (setterType.isAssignableFrom(resultType)) {
						setter = method;
						setterType = resultType;
					} else {
						throw new RuntimeException("Illegal overloaded getter method with ambiguous type for property "
								+ propName + " in class " + firstMethod.getDeclaringClass()
								+ ".  This breaks the JavaBeans "
								+ "specification and can cause unpredicatble results.");
					}
				}
				addSetMethod(propName, setter);
			}
		}
	}

	/**
	 * 公共获取实例方法,避免重复创建，提高效率
	 * 
	 * @param clazz
	 * @return ReflectUtil
	 */
	public synchronized static final Reflector getInstance(Class<?> clazz) {
		if (!cacheReflectUtil.containsKey(clazz)) {
			cacheReflectUtil.put(clazz, new Reflector(clazz));
		}
		return cacheReflectUtil.get(clazz);
	}

	/**
	 * 将类中的get方法添加到getMethods集合中
	 * 
	 * @param clazz
	 */
	private void addGetMethods(Class<?> clazz) {
		Map<String, List<Method>> conflictGetter = new HashMap<String, List<Method>>();
		Method[] methods = getClassMethods(clazz);
		for (Method method : methods) {
			String name = method.getName();
			if (name.startsWith("get") && name.length() > 3) {// 处理get方法
				if (method.getParameterTypes().length == 0) {// get方法不应该存在参数
					String property = methodNameToProperty(name);// 方法名转属性名
					// 添加冲突方法
					addConflectMethod(conflictGetter, property, method);
				}
			} else if (name.startsWith("is") && name.length() > 2) {// 当属性为boolean类型时，get方法以is开头
				if (method.getParameterTypes().length == 0) {// get方法不应该存在参数
					String property = methodNameToProperty(name);// 方法名转属性名
					// 添加冲突方法
					addConflectMethod(conflictGetter, property, method);
				}
			}
		}
		// 处理冲突方法
		resolveGetterConflicts(conflictGetter);
	}

	/**
	 * 解决Getter方法冲突
	 * 
	 * @param conflictGetter
	 */
	private void resolveGetterConflicts(Map<String, List<Method>> conflictGetter) {
		// 获取所有key,循环获取值，对集合进行判断
		// Set<String> keys = conflictGetter.keySet();
		// Iterator<String> it = keys.iterator();
		for (String propName : conflictGetter.keySet()) {
			List<Method> getters = conflictGetter.get(propName);
			Method firstMethod = getters.get(0);
			if (getters.size() == 1) {
				addGetMethod(propName, firstMethod);
			} else {
				Method getter = firstMethod;
				Class<?> getterType = getter.getReturnType();
				Iterator<Method> iterator = getters.iterator();
				Method method = null;
				while (iterator.hasNext()) {
					method = iterator.next();
					Class<?> resultType = method.getReturnType();
					if (resultType.equals(resultType)) {
						throw new RuntimeException("Illegal overloaded getter method with ambiguous type for property "
								+ propName + " in class " + firstMethod.getDeclaringClass()
								+ ".  This breaks the JavaBeans "
								+ "specification and can cause unpredicatble results.");
					} else if (getterType.isAssignableFrom(resultType)) {
						getter = method;
						getterType = resultType;
					} else {
						throw new RuntimeException("Illegal overloaded getter method with ambiguous type for property "
								+ propName + " in class " + firstMethod.getDeclaringClass()
								+ ".  This breaks the JavaBeans "
								+ "specification and can cause unpredicatble results.");
					}
				}
				addGetMethod(propName, getter);
			}
		}
	}

	/**
	 * 添加冲突的方法，当属性对应多个方法时，将对应的方法添加到List集合再放入Map集合
	 * 
	 * @param conflict
	 * @param property
	 * @param method
	 */
	private void addConflectMethod(Map<String, List<Method>> conflict, String property, Method method) {
		List<Method> list = conflict.get(property);
		if (list == null) {
			list = new ArrayList<Method>();
			conflict.put(property, list);

		}
		list.add(method);
	}

	/**
	 * 这个方法返回了包含一个类及其超类中的所有方法的数组
	 * 
	 * @param clazz
	 * @return 方法数组
	 */
	private Method[] getClassMethods(Class<?> clazz) {
		Map<String, Method> uniqueMethods = new HashMap<String, Method>();
		Class<?> currentClass = clazz;
		while (currentClass != null) {
			addUniqueMethods(uniqueMethods, currentClass.getDeclaredMethods());

			Class<?>[] interfaces = currentClass.getInterfaces();
			for (Class<?> anInterface : interfaces) {
				addUniqueMethods(uniqueMethods, anInterface.getMethods());// 接口中的方法都是public的，所以用getMethod()
			}
			currentClass = currentClass.getSuperclass();
		}
		Collection<Method> methods = uniqueMethods.values();
		return methods.toArray(new Method[methods.size()]);
	}

	private void addUniqueMethods(Map<String, Method> uniqueMethods, Method[] methods) {
		for (Method currentMethod : methods) {
			String signature = getSignature(currentMethod);

			if (!uniqueMethods.containsKey(signature)) {
				if (canAccessPrivateMethods()) {
					try {
						currentMethod.setAccessible(true);
					} catch (Exception e) {

					}
				}
				uniqueMethods.put(signature, currentMethod);
			}
		}

	}

	private boolean canAccessPrivateMethods() {
		try {
			SecurityManager securityManager = System.getSecurityManager();
			if (null != securityManager) {
				securityManager.checkPermission(new ReflectPermission("suppressAccessChecks"));
			}
		} catch (SecurityException e) {
			return false;
		}
		return true;
	}

	private String getSignature(Method method) {
		StringBuilder sb = new StringBuilder();
		Class<?> resultType = method.getReturnType();
		if (resultType != null) {
			sb.append(resultType.getName()).append("#");
		}
		sb.append(method.getName());
		Class<?>[] parameters = method.getParameterTypes();
		for (int i = 0; i < parameters.length; i++) {
			if (i == 0) {
				sb.append(':');
			} else {
				sb.append(',');
			}
			sb.append(parameters[i].getName());
		}
		return sb.toString();
	}

	private String methodNameToProperty(String name) {
		if (name.startsWith("is")) {
			name = name.substring(2);
		} else if (name.startsWith("get") || name.startsWith("set")) {
			name = name.substring(3);
		} else {
			throw new RuntimeException(
					"Error parsing property name '" + name + "'.  Didn't start with 'is', 'get' or 'set'.");
		}
		if (name.length() == 1 || (name.length() > 1 && !Character.isUpperCase(name.charAt(1)))) {
			name = name.substring(0, 1).toLowerCase(Locale.ENGLISH) + name.substring(1);
		}
		return name;
	}

	private void addGetMethod(String name, Method method) {
		if (isValidPropertyName(name)) {
			this.getMethods.put(name, method);
		}
	}

	private void addSetMethod(String name, Method method) {
		if (isValidPropertyName(name)) {
			this.setMethods.put(name, method);
		}
	}

	private boolean isValidPropertyName(String name) {
		return !(name.startsWith("$") || "serialVersionUID".equals(name) || "class".equals(name));
	}

	/**
	 * 根据属性获取值
	 * @param property
	 * @param target
	 * @return
	 */
	public Object getValue(String propertyName, Object target) {
		Object result = null;
		Method getter = getMethods.get(propertyName);
		if (getter != null) {
			try {
				result = getter.invoke(target);
			} catch (Exception e) {

			}
		}else {
			throw new RuntimeException("There is no getter for property named '" + propertyName + "' in '" + type + "'");
		}
		return result;
	}

	/**
	 * 根据属性设置值
	 * @param param
	 * @param property
	 * @param target
	 * @return
	 */
	public boolean setValue(Object param, String propertyName, Object target) {
		Method setter = setMethods.get(propertyName);
		if (setter != null) {
			try {
				setter.invoke(target, param);
				return true;
			} catch (Exception e) {
				return false;
			}
		}
		return false;
	}

}
